# Продвинутое руководство

## Переменные

Если вы хотите, чтобы игра запоминала каждый выбор игрока, то без переменных нам не обойтись.

Объявление переменных происходит в специальном блоке init. Имена и значения у переменных могут быть любые, но рекомендуется использовать короткие английские слова или цифры (цифры только в значении).

```renpy
init:
    $ choice = False # Переменная будет объявлена со значением False
    $ count = 0 # А у этой переменной будет значение 0
```

Изменение значения переменных будет выглядеть так:

```renpy{6-7}
init:
    $ choice = False # Переменная будет объявлена со значением False
    $ count = 0 # А у этой переменной будет значение 0

label my_mod:
    $ choice = True # Переменная `choice` становится равна `True`
    $ count += 1 # К переменной `count` добавляется одно очко
```

## Выборы

Если вы хотите дать игроку выбор, то вам необходимо использовать специальный блок menu:

```renpy
mt "Возьми-ка этот мешок с сахаром."

menu:
    "И куда мне его девать?":
        $ good += 1
        jump good_label
    "Да идите вы со своим сахаром на ...":
        jump bad_label
```

В приведенном примере Ольга Дмитриевна попросит нас взять мешок с сахаром, мы же можем как подчиниться и увеличить переменную "good" на 1, так и отказаться, причем в зависимости от нашего выбора события в игре могут измениться.

::: danger Будьте осторожны
Варианты ответа тоже открывают блок, и если мы не поставим после каждого двоеточие, мод может не запуститься.
:::

## Проверка условий

Что нам делать если мы хотим чтобы какие-то из возможностей были у игрока в зависимости от его выборов? Для этого то мы и использовали переменные! Сейчас же мы воспользуемся специальной конструкцией:

```renpy
label my_mod:
    if good > 0: # Если очков `good` больше 0, то прыгаем на лейбл хорошей концовки
        jump good_end
    else: # Иначе прыгаем на лейбл плохой концовки
        jump bad_end
```

Если значение переменной `good` больше нуля, то игра продолжится на метке `good_end`, если нет, то мы переходим на метку `bad_end`.

Теперь же мы постараемся реализовать выборы, в которых, в зависимости от прошлых выборов персонажа, будут добавляться или убираться различные варианты ответов:

```renpy
label my_mod:
    mt "Семен, сегодня после обеда ты будешь убираться в столовой."

    menu:
        "Почему именно я?":
            jump why_me
        "Я же уже помог вам сегодня" if good > 0:
            jump helped_today
        "Не намерен я вам тут прислуживать!" if good == 0:
            jump angry
```

В данном случае мы можем:

- возмутиться сложившейся ситуации;
- мы можем попросить ее отсрочить дежурство, если мы уже заслужили доверие вожатой;
- отправить ее куда подальше, если не помогали.
И в каждом случае нам, как создателям мода необходимо будет прописать что произойдет в каждом из этих случаев.

Проверка на значение `True` или `False` значения переменной происходит схоже:

```renpy
label my_mod:
    if Otnes_saxar == True: # Если помог отнести сахар, то Ольга хвалит нас.
        mt "Молодец, Семён, помог отнести сахар!"
    else: # Если же не помогли отнести, то Ольга нас ругает. Стерва.
        mt "Бездарность, не пионер, а размазня!"
```

::: tip Проверка условия
Обратите своё внимание, что проверка на значение переменной проходит с двумя знаками 'равно'.
Напомним, один знак равно — присваивание значения переменной, а два — проверка на значение переменной
:::

Также, переменным можно присваивать не только очки и состояния `True` или `False` — а всё, что вашей душе заблагорассудится. Приведём пример:

```renpy
init:
    $ Ya_molodec = None # Оставляем переменную без значения. Равна `None`.

label my_mod:
    $ Ya_molodec = "Krasavec" # Присваиваем переменной значение "Krasavec"
    if Ya_molodec == "Krasavec": # Если переменная равна "Krasavec", то мы хвалим себя.
        th "А я хорош!"
    else: # Иначе негодуем.
        th "Не получилось, не фортануло."
```

::: tip Разница в присвоенных значениях
Обратим внимание, что в примере выше проверка на значение переменной была с кавычками. Если бы мы сделали проверку без кавычек, то игра бы просто пропустила проверку.

Также советую запомнить, что `True` и `"True"` при проверке переменной — это два разных значения.
:::

## Использование карты

Чтобы дать игре понять, когда развернуть перед игроком карту, необходимо всего несколько простых команд.

1. Перед выводом карты нужно сбросить все прошлые зоны на ней:

```renpy
disable_all_zones() # Отключает все старые маркеры карты.
```

2. Заполняем конструкцию `set_zone()`.

Первым значением будет выделенная на карте зона, а вторым - лейбл, куда прыгать, при клике на неё.

Вот список всех зон и как они обозначены в игре:

- `me_mt_house` - Домик Семёна и Ольги Дмитриевны
- `estrade` - Сцена
- `music_club` - Музклуб
- `square` - Площадь
- `dining_hall` - Столовая
- `sport_area` - Спорткомплекс
- `beach` - Пляж
- `boat_station` - Лодочный причал
- `clubs` - Клубы
- `library` - Библиотека
- `medic_house` - Медпункт
- `camp_entrance` - Ворота в лагерь
- `forest` - Лес

Например, чтобы при нажатии на клубы мы прыгали на лейбл `day1_gone_to_clubs` (название лишь для примера) нужно реализовать такую конструкцию:

```renpy
set_zone("clubs", "day1_gone_to_clubs")
```

И в конце выводим саму карту командой:

```renpy
show_map()
```

Полный пример:

```renpy
label my_mod:
    $ disable_all_zones()
    $ set_zone("clubs", "day1_gone_to_clubs")
    $ show_map()
```

Вариант кода со всеми позициями на карте:

```renpy
label my_mod:
    $ disable_all_zones()

    $ set_zone("me_mt_house", "day1_gone_to_mt_house") # Домик Семёна и Ольги Дмитриевны
    $ set_zone("estrade", "day1_gone_to_estrade") # Сцена
    $ set_zone("music_club", "day1_gone_to_music_club") # Музклуб
    $ set_zone("square", "day1_gone_to_square") # Площадь
    $ set_zone("dining_hall", "day1_gone_to_dining_hall") # Столовая
    $ set_zone("sport_area", "day1_gone_to_sport_area") # Спорткомплекс
    $ set_zone("beach", "day1_gone_to_beach") # Пляж
    $ set_zone("boat_station", "day1_gone_to_boat_station") # Лодочный причал
    $ set_zone("clubs", "day1_gone_to_clubs") # Клубы
    $ set_zone("library", "day1_gone_to_library") # Библиотека
    $ set_zone("medic_house", "day1_gone_to_medic_house") # Медпункт
    $ set_zone("camp_entrance", "day1_gone_to_camp_entrance") # Ворота в лагерь
    $ set_zone("forest", "day1_gone_to_forest") # Лес

    $ show_map()
```

## Добавление персонажа

Любой персонаж игры - простая переменная. Переменные создаются двумя способами, методом движка - с помощью команды `define` и методом Python - с помощью символа - `$`.

```renpy
define ivan = Character(u"Иван")
```

::: tip
Символ `u` используется для отображения русского текста у людей, у которых он не установлен в системе.
:::

Теперь персонажа можно использовать в моде:

```renpy{3-4}
define ivan = Character(u"Иван")

label my_mod:
    ivan "Привет"
```

Цвет имени персонажа указывается с помощью параметра `color`, также рекомендую использовать неизменный параметр `what_color` для изменения цвета речи персонажа. В самой игре все персонажи говорят цветом `#f1d076`, так не будем выделяться: назначим нашему персонажу в качестве цвета имени, скажем, зелёный цвет (`#008000`) и установим в качестве цвета речи - `#f1d076`:

```renpy{1}
define ivan = Character(u"Иван", color="#008000", what_color="#f1d076")

label my_mod:
    ivan "Привет"
```

## Монитор с номером дня

Для выведения на экран монитора, необходимо прописать две команды:

```renpy
backdrop = "days"
new_chapter(1, u"Мой мод. День первый.")
```

- `backdrop` - фон с монитором.
- `1` - номер дня, который будет показан на мониторе.
- текст (в примере - **Мой мод. День первый.**) - то, как будет называться сохранение.

Фонов (`backdrop`) в игре 5:

```renpy
backdrop = "days" # Просто монитор.
backdrop = "un" # Монитор с проплывающей Леной.
backdrop = "us" # Монитор с проплывающей Ульяной.
backdrop = "sl" # Монитор с проплывающей Славей.
backdrop = "dv" # Монитор с проплывающей Алисой.
```

:::tip
Обратим внимание, что при использовании команды `new_chapter` сбрасываются плавные переходы для изображений, следующими после неё. Проверено на опыте. Советуем прописать `pause` в `n`-ное количество секунд, чтобы сначала проигрались плавные анимации перехода, а затем сработала команда `new_chapter`
:::

## Достижения

Для вывода достижения в нужном вам месте, вставьте следующие команды:

```renpy{4-8}
define myachiv = "mods/my_mod/images/myachiv.png"

label my_mod:
    play sound sfx_achievement
    show myachiv at achievement_trans
    with dspr
    $ renpy.pause(3, hard=True)
    hide myachiv
```

- `myachiv` - Ваше изображение с достижением.

## Аудиоканалы

Всего внутри "Бесконечного Лета" 8 аудиоканалов:
`ambience`, `music`, `sound`, `sound2`, `sound3`, `sound_loop`, `sound_loop2`, `sound_loop3`.

Если вам необходимо добавить новый аудиоканал, то в init-блоке вашего мода пропишите следующее:
`renpy.music.register_channel("<название_канала>", "<название_микшера>", True)`

Пример:

```renpy
init:
    $ renpy.music.register_channel("voices", "voice", True)
```

Есть три возможных микшера громкости: `music`, `sfx` и `voice`. Подбирайте необходимый вам микшер в соответствии с предназначением дорожки.

Если вам необходимо создать свой микшер для звука или музыки, пропишите следующее:
`renpy.sound.set_mixer("название_канала", "номер_микшера")`.

Это задает имя микшера, связанного с данным каналом. "sfx" находится на каналах от 0 до 3, а "music" - от 3 до 7.

Пример:

```renpy
init:
    $ renpy.sound.set_mixer(voices_mixer, 10)
```

## Экраны

### Предисловие

Экраны имеют две основные функции:

- Отображение информации. С помощью различных панелей, изображений, или вовсе текста. Например, экран диалогов ([`say`](https://www.renpy.org/doc/html/screen_special.html#say)) используется для отображения диалога с пользователем, отображая имя говорящего.
- Взаимодействие. Различные кнопки и панели, показанные экраном, позволяют игроку совершать различные действия или менять какие-либо значения.

Экраны могут отображаться четырьмя способами:

- Неявно, как, например, экран `say`.
- Явно, используя операторы, вызывающие отображения других экранов.
- Автоматически. Например, экран с именем [`main_menu`](https://www.renpy.org/doc/html/screen_special.html#main-menu) будет автоматически вызываться, когда игрок вернётся в главное меню игры или модификации, если она, конечно, заменяет этот экран на свой.
- Как действие, связанное с любой кнопкой. Например, можно привязать появление какого-либа скрина на нажатие правой/левой кнопки мыши или при нажатии на клавишу.

### Язык экрана

Он состоит из оператора, объявляющего новый экран ([`screen`](https://www.renpy.org/doc/html/screens.html?highlight=screen#screen-statement)), операторов, добавляющих отображаемые объекты на этот экран (например, [`add`](https://www.renpy.org/doc/html/screens.html?highlight=screen#add) или [`imagebutton`](https://www.renpy.org/doc/html/screens.html?highlight=screen#imagebutton)), и [операторов управления](https://www.renpy.org/doc/html/style_properties.html) (`xalign`, `yalign`, `spacing`).

Пример экрана:

```renpy
screen test_screen:
    tag test
    modal True

    add "test bg"
```

В этом примере, первая строка — оператор screen, что используется для объявления экрана. Не забываем про то, что необходимо дать экрану имя. Оператор `tag` выступает в выдаче "тэга" для этого окна, заменяя другие экраны с таким же тегом, если вызывается ещё один экран с таким же тегом. Оператор `modal` при значении `True` не позволяет игроку взаимодействивать с отображаемыми объектами "под" экраном, давая взаимодействие игроку лишь с кнопками или объектами внутри самого экрана. Оператор `add` добавляет изображение или любой отображаемый объект на экран. Возможно расположение по указанным координатам, благодаря стандартным командам из трансформов (`align`, `pos` и прочее).

### Синтаксис языка экрана

У операторов экрана в большинстве своём общий синтаксис. Если оператор заканчивается двоеточием, то он создаёт блок, в котором может быть одно из следующих составляющих:

- Список свойств (`modal True`, `tag menu`, `sensitive`).
- Операторы языка экрана (`imagebutton`, `textbutton`, `text` и прочее).

### Операторы-кнопки

[`button`](https://www.renpy.org/doc/html/screens.html?highlight=screen#button), `imagebutton`, [`textbutton`](https://www.renpy.org/doc/html/screens.html?highlight=screen#textbutton) — все эти операторы позволяют создать внутри экрана кнопку, которой можно задать свойства (`hover`, `idle`, `action` и т.д). Как понятно из названия, `imagebutton` создаёт кнопку-изображение, `textbutton` — текстовую кнопку, а `button` — просто область экрана, при нажатии на которую будет выполнено какое-либо действие, указанное для такой кнопки.

Пример экрана с кнопками:

```renpy
screen my_screen:
    tag menu
    modal True

    add "black"

    textbutton "Текстовая кнопка":
        align(0.5, 0.5) # Располагаем кнопку по центру экрана

    imagebutton:
        align(0.1, 0.1) # Располагаем кнопку в левом верхнем углу экрана
        idle "Путь до изображения-кнопки в состоянии 'не наведена мышка'"
        hover "Путь до изображения-кнопки в состоянии 'наведена мышка'"
        action NullAction() # Даём кнопке "нулевое действие" — при нажатии ничего не происходит.
```

::: tip Оператор "auto"
Также, если вы не хотите прописывать idle и hover версию в две строчки, можно воспользоваться оператором `auto`, который автоматически находит idle и hover версию кнопки в зависимости от названия.

Учтите, что в названиях кнопки должны присутствовать слова `idle` и `hover`.

Пример:

```renpy
screen my_screen:
    tag menu
    modal True

    imagebutton:
        auto "mymod/buttons/my_button_%s.png" # Слово, на которое опирается оператор `auto` находится в конце. То бишь, названия кнопок с `idle` и `hover` версией — my_button_idle и my_button_hover.
```

:::

Полный список действий (`action`), которые можно дать кнопкам, можно найти [здесь](https://www.renpy.org/doc/html/screen_actions.html).

### Операторы экрана

В дополнение к оператору screen, есть три оператора, которые включают экраны. Это [`show screen`](https://www.renpy.org/doc/html/screen_actions.html?highlight=screen#Show), [`call screen`](https://www.renpy.org/doc/html/screen_actions.html?highlight=screen#Call) и [`hide screen`](https://www.renpy.org/doc/html/screen_actions.html?highlight=screen#Hide).

Оператор `show screen` вызывает отображение экрана. Возможно использование `with`. Также, их можно вызывать с какими-либо параметрами. Например, `show screen my_screen(_layer = "screens")`.

```renpy
screen my_screen:
    add "black"

label my_mod:
    show my_screen with dissolve # Показываем экран с растворением в 0.5 секунды.
```

::: tip
Учтите, что screen прописываются за блоком лейбла. Можно прописать вне всяких блоков, а также внутри блока `init`.
:::

Показанные таким образом экраны отображаются до тех пор, пока они не будут скрыты командой `hide screen`.

Оператор `hide screen` используется для скрытия экрана, который отображается в данный момент. Возможно использование `with`.

```renpy
screen my_screen:
    add "black"

label my_mod:
    show screen my_screen with dissolve # Показываем экран с растворением в 0.5 секунды.
    pause(2) # Пауза 2 секунды
    hide screen my_screen with dissolve # Убираем экран с растворением в 0.5 секунды.
```

Оператор `call screen` показывает экран, а затем снова скрывает его, как только с ним закончится взаимодействие (Скажем, когда будет произведён прыжок на другой лейбл). Убрать экран можно также с помощью действия [`return`](https://www.renpy.org/doc/html/screen_actions.html?highlight=screen#Return) (или `Return()`, если мы говорим о действии для кнопки. При нажатии на неё, экран будет скрыт. Если же экран был показан с помощью `show screen` — нас выбросит в главное меню игры). Возможно использование `with`.

```renpy
screen my_screen:
    add "black"

    textbutton "Нажми!":
        action Return() # Закрываем экран при нажатии на кнопку.

label my_mod:
    call screen my_screen with dissolve # Показываем экран с растворением в 0.5 секунды. Как только будет нажата кнопка, экран будет закрыт.
```

::: tip Различие между Show и ShowMenu при задавании действии для кнопки.
Если внутри экрана мы откроем ещё один экран, который при нажатии на кнопку был вызван действием `Show` (`Show("my_screen")`) и затем нажмём на кнопку с действием `Return()`, то нас выбросит в главное меню. Но! Если мы также вызовем экран, но с действием `ShowMenu` (`ShowMenu("my_screen")`), то при нажатии на кнопку с `Return()` внутри второго экрана, нас возвратит обратно на предыдущий экран. Удобно!

```renpy
screen my_screen:
    add "black"

    textbutton "Нажми!":
        action Show("my_screen2") # Открываем экран "my_screen2"

screen my_screen2:
    add "black"

    textbutton "Нажми!":
        action Return() # Нас выбрасывает в главное меню игры.
```

и

```renpy
screen my_screen:
    add "black"

    textbutton "Нажми!":
        action ShowMenu("my_screen2") # Открываем экран "my_screen2"

screen my_screen2:
    add "black"

    textbutton "Нажми!":
        action Return() # Нас возвращает обратно на экран "my_screen"
```

:::

## Портирование мода на Android с помощью ESTool

::: danger
Необходимо наличие установленного [Python](https://www.python.org/) и, для использования полного функционала, установленный модуль [Pillow (PIL)](https://pypi.org/project/Pillow/)
:::

Благодаря утилите ESTool можно облегчить портирование своего мода в несколько раз, т.к она позволяет автоматически урезать размеры изображений/кнопок/стилей и т.п до тех, что поддерживает мобильная версия Бесконечного Лета, сохраняя огромное количество времени. Создано группой ["Загрузчик модов для Бесконечного Лета"](https://vk.com/esmanager), за что им большое спасибо. Программа всё ещё в разработке, так что ошибки не исключены.

- [Обсуждение](https://vk.com/topic-93304648_35130916?offset=0)
- [Ссылка на скачивание всех версий ESTool](https://disk.yandex.ru/d/xfQBrr-_IVDZXQ)

В нашем примере используется версия `1.001`.

### Установка

Выбираем версию, скачиваем, распаковываем архив в любую папку.

### Настройка

Открываем `config.py`, смотрим.

- `dry_run` — делает прогон кода, если значение переменной равно `True`. Ничего не меняет и не создает никаких файлов (кроме лога), но выявляет большую часть ошибок.
  Настоятельно рекомендуется сначала запустить с `True`, посмотреть лог, а уже затем ставить на `False` и запускать скрипт
- `in_place` — если `False`, то портированный мод кладется в отдельную папку рядом. Если `True`, то он кладется **вместо** исходных файлов.
- `backup_style` — создание бэкапа скриптов. Если `in_place` равно `True`, то создаются резервные копии rpy-скриптов (и только скриптов!)
  - При значении `separate` бэкапы создаются в отдельной папке.
  - Если `nearby`, то бэкап кладётся рядом с исходными файлами (с расширением `._rpy`).
  - Если `None`, то бэкапы не создаются.
    Если `in_place` равно `False`, то настройка игнорируется (в бэкапах нет необходимости, поскольку исходные файлы остаются в целости и сохранности)
- `backup_location` — при `backup_style` со значением `separate` задаёт путь к папке с бэкапами.
- `src_path` — путь к папке с модом, который требуется портировать.
- `dest_path` — путь, куда класть результат. При `in_place` со значением `True` заменяет путь в `src_path` на `dest_path`
- `rename_non_ascii` — при значении `True` переименовывает файлы в латиницу, транслитерируя кириллицу при наличии.
- `rescale_coordinates` — при значении `True` умножает координаты в коде на 2/3, подгоняя под размеры для Android.
- `comments_style` — дописывание комментария к изменённым строкам в коде.
  - Если равно `long`, то в комментарий пишется исходная строка целиком.
  - Если равно `short`, то делается пометка, что строка была изменена.
  - Если равно `none`, то комментарий не пишется.
    ::: danger
    Если при `in_place` со значением `True` запустить скрипт дважды с `comments_style` со значением `none`, то координаты будут изменены дважды!
    :::
- `resize_images` — если `True`, сжимает изображения в полтора раза.

  ::: warning
  Требуется наличие модуля `Pillow`
  :::

  ::: danger
  Если при `in_place` со значением `True` запустить скрипт дважды, то картинки будут сжаты дважды!
  :::

- `old_virtual_root` — если в коде скриптов для доступа к файлам используется какой-то префикс, не отраженный в структуре каталогов (например, `"mods/"`), следует вписать его сюда. Иначе не будет работать замена путей.
- `new_virtual_root` — если в ходе портирования требуется добавить постфикс, он указывается здесь.
- `exclude_patterns` — шаблоны файлов, которые следует исключить из мода.

  - `?` — любой одиночный символ.
  - `*` — любое количество любых символов.
  - `**` — любые подпапки

  Примеры исключений:

  - `**.rpyc` — скомпилированные в байт-код скрипты.
  - `"**/Thumbs.db` — свойства папки (Windows)
  - `**/.DS_Store` — свойства папки (macOS)
  - `**._rpy`, # Наши бэкапы при `backup_style` равным `nearby` (старые удаляются, новые создаются).

- `path_rewrite_rules` — правила по перемещению файлов. Синтаксис: `префикс, шаблон, новый_префикс`.

  Если путь к файлу начинается с указанного префикса и удовлетворяет шаблону, то этот префикс срезается и заменяется новым. Файл копируется/перемещается (в зависимости от `in_place`) в новое место; также переписываются пути в скриптах.

  ::: warning
   Правила применяются последовательно, так что порядок объявления важен.
  :::

  ::: danger
  Обязательно наличие одного правила: `("/мод/", "**", "/мод/")`. Под это правило попадают все загружаемые в скриптах файлы, но оно ничего не меняет. Необходимо для корректного переписывания путей в коде.
  :::

- `extra_files` — дополнительные файлы, которые требуется добавить в мод.

  ::: danger
  Обязательно наличие одного правила: `("sprites_lol.rpy", "/")`. Добавляет `rpy` с мобильными версиями оригинальных спрайтов.
  :::

### Запуск

Запускаем командную строку, перемещаемся в папку с ESTool с помощью `cd`, запускаем скрипт: `python estool-1.001.py`. Прогресс портирования также отображается. По окончанию нажмите `Enter` и закройте ком. строку.

### Заключение

Как уже упоминалось ранее, скрипт неполноценен и некоторые места в коде может не обработать, потому, на всякий случай открываем и внимательно просматриваем свои `rpy` на наличие изменений, правим.
